package name.zeno.qrcode.library

import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import com.google.zxing.*
import com.google.zxing.common.HybridBinarizer
import com.google.zxing.qrcode.QRCodeReader
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import java.io.FileOutputStream
import java.io.IOException
import java.util.*

/**
 * 二维码生成工具类
 * 创建时间: 2015-08-11 22:17:08
 *
 * @author 陈治谋
 */
internal object QRCodeUtil {
  private val TAG = "QRCodeUtil"
  var isDebug = BuildConfig.DEBUG

  /**
   * 生成二维码Bitmap
   *
   * @param content   内容
   * @param widthPix  图片宽度
   * @param heightPix 图片高度
   * @param logo    二维码中心的Logo图标（可以为null）
   * @param filePath  用于存储二维码图片的文件路径
   * @return 生成二维码及保存文件是否成功
   */
  fun createQRImage(content: String?, widthPix: Int, heightPix: Int, logo: Bitmap?, filePath: String): Boolean {
    try {
      if (content.isNullOrEmpty()) return false

      //配置参数
      val hints = HashMap<EncodeHintType, Any>()
      hints.put(EncodeHintType.CHARACTER_SET, "utf-8")
      //容错级别
      hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H)
      //设置空白边距的宽度
      //            hints.put(EncodeHintType.MARGIN, 2); //default is 4

      // 图像数据转换，使用了矩阵转换
      val bitMatrix = QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, widthPix, heightPix, hints)
      val pixels = IntArray(widthPix * heightPix)
      // 下面这里按照二维码的算法，逐个生成二维码的图片，
      // 两个for循环是图片横列扫描的结果
      for (y in 0 until heightPix) {
        for (x in 0 until widthPix) {
          pixels[y * widthPix + x] = when {
            bitMatrix.get(x, y) -> Color.GRAY
            else -> Color.WHITE
          }
        }
      }

      // 生成二维码图片的格式，使用ARGB_8888
      var bitmap: Bitmap = Bitmap.createBitmap(widthPix, heightPix, Bitmap.Config.ARGB_8888)
      bitmap.setPixels(pixels, 0, widthPix, 0, 0, widthPix, heightPix)

      if (logo != null) {
        bitmap = addLogo(bitmap, logo)
      }

      //必须使用compress方法将bitmap保存到文件中再进行读取。直接返回的bitmap是没有任何压缩的，内存消耗巨大！
      return bitmap.compress(Bitmap.CompressFormat.JPEG, 100, FileOutputStream(filePath))
    } catch (e: WriterException) {
      if (isDebug) e.printStackTrace()
    } catch (e: IOException) {
      if (isDebug) e.printStackTrace()
    }

    return false
  }

  /**
   * - [利用zxing识别图片二维码](http://blog.csdn.net/a102111/article/details/48377537)
   */
  fun decode(bmp: Bitmap): String? {

    var httpString: String? = null

    val data = getYUV420sp(bmp.width, bmp.height, bmp)
    // 处理
    try {
      val hints = Hashtable<DecodeHintType, Any>()
      hints[DecodeHintType.CHARACTER_SET] = "utf-8"
      hints[DecodeHintType.TRY_HARDER] = true
      hints[DecodeHintType.POSSIBLE_FORMATS] = BarcodeFormat.QR_CODE
      val source = PlanarYUVLuminanceSource(data, bmp.width, bmp.height, 0, 0, bmp.width, bmp.height, false)
      val bitmap1 = BinaryBitmap(HybridBinarizer(source))
      val reader2 = QRCodeReader()
      val result = reader2.decode(bitmap1, hints)

      httpString = result.text
    } catch (e: Exception) {
      e.printStackTrace()
    }

    return httpString
  }


  /**
   * YUV420sp
   *
   * @param inputWidth
   * @param inputHeight
   * @param scaled
   * @return
   */
  private fun getYUV420sp(inputWidth: Int, inputHeight: Int, scaled: Bitmap): ByteArray {
    val argb = IntArray(inputWidth * inputHeight)

    scaled.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight);
    val yuv = ByteArray(inputWidth * inputHeight * 3 / 2)

    encodeYUV420SP(yuv, argb, inputWidth, inputHeight);

    scaled.recycle();

    return yuv;
  }

  /**
   * RGB转YUV420sp
   *
   * @param yuv420sp
   * inputWidth * inputHeight * 3 / 2
   * @param argb
   * inputWidth * inputHeight
   * @param width
   * @param height
   */
  private fun encodeYUV420SP(yuv420sp: ByteArray, argb: IntArray, width: Int, height: Int) {
    // 帧图片的像素大小
    val frameSize = width * height
    // ---YUV数据---
    var Y: Int
    var U: Int
    var V: Int
    // Y的index从0开始
    var yIndex = 0
    // UV的index从frameSize开始
    var uvIndex = frameSize

    // ---颜色数据---
    //      int a, R, G, B;
    var R: Int
    var G: Int
    var B: Int
    //
    var argbIndex = 0
    //

    // ---循环所有像素点，RGB转YUV---
    for (j in 0 until height) {
      for (i in 0 until width) {

        // a is not used obviously
        //              a = (argb[argbIndex] & 0xff000000) >> 24;
        R = argb[argbIndex] and 0xff0000 shr 16
        G = argb[argbIndex] and 0xff00 shr 8
        B = argb[argbIndex] and 0xff
        //
        argbIndex++

        // well known RGB to YUV algorithm
        Y = (66 * R + 129 * G + 25 * B + 128 shr 8) + 16
        U = (-38 * R - 74 * G + 112 * B + 128 shr 8) + 128
        V = (112 * R - 94 * G - 18 * B + 128 shr 8) + 128

        //
        Y = Math.max(0, Math.min(Y, 255))
        U = Math.max(0, Math.min(U, 255))
        V = Math.max(0, Math.min(V, 255))

        // NV21 has a plane of Y and interleaved planes of VU each
        // sampled by a factor of 2
        // meaning for every 4 Y pixels there are 1 V and 1 U. Note the
        // sampling is every other
        // pixel AND every other scanline.
        // ---Y---
        yuv420sp[yIndex++] = Y.toByte()
        // ---UV---
        if (j % 2 == 0 && i % 2 == 0) {
          //
          yuv420sp[uvIndex++] = V.toByte()
          //
          yuv420sp[uvIndex++] = U.toByte()
        }
      }
    }
  }


  /**
   * 在二维码中间添加Logo图案, 失败会直接返回二维码原图
   */
  private fun addLogo(src: Bitmap, logo: Bitmap): Bitmap {
    //获取图片的宽高
    val srcWidth = src.width
    val srcHeight = src.height
    val logoWidth = logo.width
    val logoHeight = logo.height

    //logo大小为二维码整体大小的1/5
    val scaleFactor = srcWidth.toFloat() / 5 / logoWidth
    var bitmap: Bitmap? = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888)
    try {
      val canvas = Canvas(bitmap!!)
      canvas.drawBitmap(src, 0f, 0f, null)
      canvas.scale(scaleFactor, scaleFactor, srcWidth / 2F, srcHeight / 2F)
      canvas.drawBitmap(logo, (srcWidth - logoWidth) / 2F, (srcHeight - logoHeight) / 2F, null)

      canvas.save(Canvas.ALL_SAVE_FLAG)
      canvas.restore()
    } catch (e: Exception) {
      bitmap = null
      e.stackTrace
    }

    return bitmap ?: src
  }
}
